﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json.Linq;
using Nx.Cache;
using System.Net;
using System.Text;

namespace Nx.Filter
{
    public class RedisNxAttribute : Attribute, IAsyncActionFilter
    {
        /// <summary>
        /// Nx键的名称
        /// 例如：CustomName
        /// 可加入一定的自定义参数
        /// 例如：CustomName_{Param.Name}
        /// </summary>
        public string KeyName { get; set; } = string.Empty;

        /// <summary>
        /// 缓存的过期时间
        /// </summary>
        public int TimeoutSeconds { get; set; } = 5;

        /// <summary>
        /// 是否自动删除
        /// </summary>
        public bool Delete { get; set; } = true;

        /// <summary>
        /// 提示的信息
        /// </summary>
        public string Message { get; set; } = "请求过于频繁，请{0}稍后再试";

        public RedisNxAttribute() { }

        public RedisNxAttribute(string key)
        {
            KeyName = key;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            var httpContext = context.HttpContext;
            var request = httpContext.Request;
            var nxKeyName = FormatNxKey(request);

            if (request.Method == HttpMethod.Post.Method)
            {
                // 这里只是拿到POST的参数字符串
                var postBody = GetBody(request);
                if (string.IsNullOrWhiteSpace(postBody) == false)
                {
                    // 就当POST上来的是可以JSON转换的
                    FormatPostJson(postBody, ref nxKeyName);
                    // 自行添加
                }
            }
            else
            {
                // 针对GET请求参数的处理
                FormatGet(request, ref nxKeyName);
            }

            // 
            if (string.IsNullOrWhiteSpace(nxKeyName) == false)
            {
                // NX特性，不存在将写入成功，否则写入失败
                if (await CacheHelper.client.SetNxAsync(nxKeyName, KeyName, TimeoutSeconds) == false)
                {
                    // 写入失败，获取当前这个缓存的剩余时间
                    var ttl = await CacheHelper.client.TtlAsync(nxKeyName);
                    var time = FormatTime(ttl);
                    var message = Message.Replace("{0}", time);

                    // 将信息返回
                    context.Result = new ContentResult
                    {
                        StatusCode = (int)HttpStatusCode.BadRequest,
                        Content = message
                    };
                    return;
                }
                httpContext.Items.Add(KeyName, nxKeyName);

            }

            await next();

            // 响应阶段

            // 需要删除缓存Key
            if (Delete)
            {
                if (httpContext.Items.TryGetValue(KeyName, out var itemValue)
                    && itemValue is string deleteKey
                    && string.IsNullOrWhiteSpace(deleteKey) == false)
                {
                    CacheHelper.client.Del(deleteKey);
                }

            }
        }

        /// <summary>
        ///     创建那个缓存Key
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        string FormatNxKey(HttpRequest request)
        {
            var keys = new string[3] { "Nx", GetRoutePath(request), KeyName }.Where(t => string.IsNullOrWhiteSpace(t) == false);
            return string.Join(":", keys);
        }

        /// <summary>
        ///     获取当前访问的路由
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        string GetRoutePath(HttpRequest request)
        {
            try
            {
                request.RouteValues.TryGetValue("controller", out var controller);
                request.RouteValues.TryGetValue("action", out var action);
                return $"{controller}_{action}".Trim('_');
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message}\r\n{ex.StackTrace}\r\n{ex.Source}");
                return "Empty";
            }
        }


        /// <summary>
        ///     格式化GET请求的参数
        /// </summary>
        /// <param name="request"></param>
        /// <param name="nxKeyName"></param>
        void FormatGet(HttpRequest request, ref string nxKeyName)
        {
            try
            {
                if (request.Query.Count > 0)
                {
                    foreach (var kv in request.Query)
                    {
                        nxKeyName = nxKeyName.Replace($"{{Param.{kv.Key}}}", kv.Value.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message}\r\n{ex.StackTrace}\r\n{ex.Source}");
            }
        }

        /// <summary>
        ///     格式化缓存过期时间
        /// </summary>
        /// <param name="seconds"></param>
        /// <returns></returns>
        string FormatTime(long seconds)
        {
            var result = new StringBuilder();
            try
            {
                TimeSpan time = TimeSpan.FromSeconds(seconds);
                if (time.Days > 0)
                {
                    result.Append($"{time.Days}天");
                }
                if (time.Hours > 0)
                {
                    result.Append($"{time.Hours}时");
                }
                if (time.Minutes > 0)
                {
                    result.Append($"{time.Minutes}分");
                }
                if (time.Seconds > 0)
                {
                    result.Append($"{time.Seconds}秒");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message}\r\n{ex.StackTrace}\r\n{ex.Source}");
            }
            return result.ToString();
        }

        /// <summary>
        ///     获取POST提交的参数
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        string GetBody(HttpRequest request)
        {
            var postValue = string.Empty;
            try
            {
                if (request?.ContentLength > 0)
                {
                    request.Body.Seek(0, SeekOrigin.Begin);
                    // 使用这个方式读取，并且使用异步
                    StreamReader streamReader = new StreamReader(request.Body, Encoding.UTF8);
                    postValue = streamReader.ReadToEndAsync().Result;
                    request.Body.Seek(0, SeekOrigin.Begin);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message}\r\n{ex.StackTrace}\r\n{ex.Source}");
            }
            return postValue;
        }

        /// <summary>
        ///     
        /// </summary>
        /// <param name="postValue"></param>
        /// <param name="key"></param>
        void FormatPostJson(string postValue, ref string key)
        {
            try
            {
                var jObject = JObject.Parse(postValue);
                foreach (var i in jObject.Properties())
                {
                    key = key.Replace($"{{Param.{i.Name}}}", i.Value.ToString());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message}\r\n{ex.StackTrace}\r\n{ex.Source}");
            }
        }


    }
}
